/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package org.apache.druid.server.lookup;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeName;
import com.google.common.annotations.VisibleForTesting;
import org.apache.druid.java.util.common.logger.Logger;
import org.apache.druid.query.lookup.LookupExtractorFactory;
import org.apache.druid.query.lookup.LookupIntrospectHandler;
import org.apache.druid.server.lookup.cache.polling.PollingCacheFactory;
import org.joda.time.Period;

import javax.annotation.Nullable;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;

@JsonTypeName("pollingLookup")
public class PollingLookupFactory implements LookupExtractorFactory
{
  private static final Logger LOGGER = new Logger(PollingLookupFactory.class);

  @JsonProperty("pollPeriod")
  private final Period pollPeriod;

  @JsonProperty("dataFetcher")
  private final DataFetcher dataFetcher;

  @JsonProperty("cacheProvider")
  private final PollingCacheFactory cacheFactory;

  private final PollingLookup pollingLookup;

  private final AtomicBoolean started = new AtomicBoolean(false);

  private final String id = Integer.toHexString(System.identityHashCode(this));

  @JsonCreator
  public PollingLookupFactory(
      @JsonProperty("pollPeriod") Period pollPeriod,
      @JsonProperty("dataFetcher") DataFetcher dataFetcher,
      @JsonProperty("cacheFactory") PollingCacheFactory cacheFactory
  )
  {
    this.pollPeriod = pollPeriod == null ? Period.ZERO : pollPeriod;
    this.dataFetcher = dataFetcher;
    this.cacheFactory = cacheFactory;
    this.pollingLookup = new PollingLookup(
        this.pollPeriod.toStandardDuration().getMillis(),
        this.dataFetcher,
        this.cacheFactory
    );
  }

  @VisibleForTesting
  protected PollingLookupFactory(
      Period pollPeriod,
      DataFetcher dataFetcher,
      PollingCacheFactory pollingCacheFactory,
      PollingLookup pollingLookup
  )
  {
    this.pollPeriod = pollPeriod;
    this.dataFetcher = dataFetcher;
    this.cacheFactory = pollingCacheFactory;
    this.pollingLookup = pollingLookup;
  }

  @Override
  public boolean start()
  {
    synchronized (started) {
      if (!started.get()) {
        LOGGER.info("started polling lookup [%s]", id);
        started.set(pollingLookup.isOpen());
      }
      return started.get();
    }
  }

  @Override
  public boolean close()
  {
    synchronized (started) {
      if (started.getAndSet(false)) {
        LOGGER.info("closing polling lookup [%s]", id);
        pollingLookup.close();
      }
      return !started.get();
    }
  }

  @Override
  public boolean replaces(
      @Nullable LookupExtractorFactory lookupExtractorFactory
  )
  {
    if (lookupExtractorFactory == null) {
      return true;
    }

    return !this.equals(lookupExtractorFactory);
  }

  @Nullable
  @Override
  public LookupIntrospectHandler getIntrospectHandler()
  {
    //not supported yet
    return null;
  }

  @Override
  public void awaitInitialization()
  {
  }

  @Override
  public boolean isInitialized()
  {
    return true;
  }

  @Override
  public PollingLookup get()
  {
    return pollingLookup;
  }

  @Override
  public boolean equals(Object o)
  {
    if (this == o) {
      return true;
    }
    if (!(o instanceof PollingLookupFactory)) {
      return false;
    }

    PollingLookupFactory that = (PollingLookupFactory) o;

    if (pollPeriod != null ? !pollPeriod.equals(that.pollPeriod) : that.pollPeriod != null) {
      return false;
    }
    if (dataFetcher != null ? !dataFetcher.equals(that.dataFetcher) : that.dataFetcher != null) {
      return false;
    }
    return cacheFactory != null
           ? (that.cacheFactory != null
              && cacheFactory.getClass() == (that.cacheFactory).getClass())
           : that.cacheFactory == null;
  }

  @Override
  public int hashCode()
  {
    return Objects.hash(pollPeriod, dataFetcher, cacheFactory);
  }
}
